<!--
 * @Author: 明月下
 * @Date: 2022-09-20 20:44:08
 * @LastEditors: 明月下
 * @LastEditTime: 2023-03-31 23:14:31
 * @FilePath: \minesweeper\src\App.vue
 * @Description:js-confetti
-->
<script setup lang="ts">
import JsConfetti from "confetti-js"
import VueConfetti from 'vue-confetti'
import qs from 'qs'
import axios from 'axios'
import ConfettiExplosion from "vue-confetti-explosion"
import { computed, ComputedRef, reactive, Ref, ref } from 'vue'
import GridItem from './components/GridItem.vue'
// 定义变量
let inputValue = ref<string>('')
let showCommit = ref<boolean>(false)
let commitLoading = ref<boolean>(false)
const rows = ref(10) // 行数
const columns = ref(10) // 列数
const boobNumber = ref(10) // 炸弹数
const total = computed(() => rows.value * columns.value) // 总格子数
const isGameOver = ref(false)
const isGameSuccess = ref(false)
const gridItems = ref()
const opened = ref(0)
const flagNum = ref(0)
let time = ref<number>(0)
let confetti = ref(false)
const level = ref(0) // 难度设置 0:简单 1：中等 2:困难
const gridStyle = computed(() => {
  return `--row:${rows.value};--column:${columns.value}`
})
// 随机地雷位置 方法1
// const arr = new Array(total.value).fill(0)
// let boob = boobNumber.value
// while(boob > 0) {
//   const index = total.value * Math.random() >> 0
//   if (arr[index] === 1) {
//     continue;
//   } else {
//     arr[index] = 1
//     boob--
//   }
// }
// 方法2
// let arr = [...new Array(boobNumber.value).fill(1), ...new Array(total.value - boobNumber.value).fill(0)]
// let arr: number[] = new Array(total.value).fill(0).fill(9, 0, boobNumber.value)
// arr = arr.sort((a,b) => Math.random() - 0.5)
// // 待优化初版
// const grid: ComputedRef<{ isBoob: boolean; count: number; }[]> = computed(() => {
//   let arr: number[] = new Array(total.value).fill(0).fill(1, 0, boobNumber.value) // 初始化数组，并填入地雷
//   arr = arr.sort((a,b) => Math.random() - 0.5) // 随机排序
//   return arr.map((item: number, index): {isBoob: boolean; count: number;} => {
//     // 通过index计算位置
//     const units:number = index%rows.value
//     const tens:number = index/columns.value >>0
//     let count = 0
//     // 嵌套for循环来计算几节点，同时Math.min和Math.max用来判定边界
//     for (let i = Math.max(0, tens-1); i < Math.min(tens+2, columns.value);i++){
//       for (let j = Math.max(0, units-1); j < Math.min(units+2, rows.value);j++){
//         if (arr[i*columns.value+j] === 1 && !(i === j && j === i)) {
//           count++
//         }
//       }
//     }
//     // 返回对象组成的数组，isBoob表示是否为炸弹节点，count是附近地雷数
//     return {
//       isBoob: !item,
//       count
//     }
//   })
// })
// // 版本一：几乎没提升，但是看起来厉害一点的优化
// const grid = computed(() => {
//   let arr:number[] = new Array(total.value).fill(0).fill(1, 0, boobNumber.value)
//   arr = arr.sort((a,b) => Math.random() - 0.5)
//   // 位移数组
//   const list: number[][] = [[-1,-1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]
//   return arr.map((item, index) => {
//     const units:number = index%rows.value
//     const tens:number = index/columns.value >>0
//     let count:number = 0
//     // 循环位移数组来判断周围节点
//     list.map(i => {
//       const y:number = tens+i[0] >= 0 ? (tens+i[0] < columns.value ? tens+i[0] : -1) :-1
//       const x:number = units+i[1] >= 0 ? (units+i[1] < rows.value ? units+i[1] : -1) :-1
//       if (arr[y*columns.value +x] && x>=0 && y>=0){
//         count++
//       }
//     })
//     return {
//       isBoob: item ? true : false,
//       count
//     }
//   })
// })
// // 版本二：一样的遍历，但只计算地雷节点
// const grid = computed(() => {
//   let arr: number[] = new Array(total.value).fill(0).fill(1, 0, boobNumber.value)
//   arr = arr.sort((a,b) => Math.random() - 0.5)
//   const list: number[][] = [[-1,-1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]
//   const grid = arr.map((item) => {return { isBoob: item ? true : false, count: 0}} ) // 先赋值转换为对线的数组
//   grid.map((item, index) => {
//     // 判断地雷节点进行计算
//     if(item.isBoob) {
//       const units:number = index%rows.value
//       const tens:number = index/columns.value >>0
//       list.map(i => {
//         const y:number = tens+i[0] >= 0 ? (tens+i[0] < columns.value ? tens+i[0] : -1) :-1
//         const x:number = units+i[1] >= 0 ? (units+i[1] < rows.value ? units+i[1] : -1) :-1
//         if (x>=0 && y>=0){
//           grid[y*columns.value +x].count++
//         }
//       })
//     }
//   })
//   return grid
// })
// // 版本三 赋值和计算都不去全部遍历
// const grid = computed(() => {
//   // 数字大于等于9就为地雷节点
//   let arr: number[] = new Array(total.value).fill(0).fill(9, 0, boobNumber.value)
//   arr = arr.sort((a,b) => Math.random() - 0.5)
//   const list: number[][] = [[-1,-1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]
//   // 还是会遍历一遍全部数组
//   // arr.forEach((item, index) => {
//   //   if (item === 9) {
//   //     const units:number = index%rows.value
//   //     const tens:number = index/columns.value >>0
//   //     list.map(i => {
//   //       const y:number = tens+i[0] >= 0 ? (tens+i[0] < columns.value ? tens+i[0] : -1) :-1
//   //       const x:number = units+i[1] >= 0 ? (units+i[1] < rows.value ? units+i[1] : -1) :-1
//   //       if (x>=0 && y>=0 && arr[y*columns.value +x] !== 9){
//   //         arr[y*columns.value +x]++
//   //       }
//   //     })
//   //   }
//   // })
//   // 最多会遍历一遍，但是可能会提前找到所以地雷而终止循环
//   for (let i=0,b=boobNumber.value;i<total.value && b>0;i++) {
//     if (arr[i] >= 9) {
//       const units:number = i%rows.value
//       const tens:number = i/columns.value >>0
//       list.map(i => {
//         const y:number = tens+i[0] >= 0 ? (tens+i[0] < columns.value ? tens+i[0] : -1) :-1
//         const x:number = units+i[1] >= 0 ? (units+i[1] < rows.value ? units+i[1] : -1) :-1
//         if (x>=0 && y>=0){
//           arr[y*columns.value +x]++
//         }
//       })
//       b--
//     }
//   }
//   return arr
// })
// 最终版本
// const grid = computed(() => {
//   let arr: number[] = new Array(total.value).fill(0)
//   const list: number[][] = [[-1,-1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]
//   // 循环炸弹数量
//   for (let b=0;b<boobNumber.value;) {
//     // 随机数找index
//     const index = Math.random()*total.value >>0
//     if (arr[index] <= 9) {
//       // 此处没炸弹则放入炸弹，并循环数减1
//       b++
//       arr[index] = 9
//       const units:number = index%rows.value
//       const tens:number = index/columns.value >>0
//       list.map(i => {
//         const y:number = tens+i[0] >= 0 ? (tens+i[0] < columns.value ? tens+i[0] : -1) :-1
//         const x:number = units+i[1] >= 0 ? (units+i[1] < rows.value ? units+i[1] : -1) :-1
//         if (x>=0 && y>=0){
//           arr[y*columns.value +x]++
//         }
//       })
//     }
//   }
//   return arr
// })
const grid:Ref<number[]> = ref([])
// 启动游戏
const doStartGame = (event: any) => {
  isGameOver.value = false
  isGameSuccess.value = false
  time.value = 0
  opened.value = 0
  flagNum.value = 0
  confetti.value = false
  // showCommit.value = false
  let arr: number[] = new Array(total.value).fill(0)
  const list: number[][] = [[-1,-1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]
  // 循环炸弹数量
  for (let b=0;b<boobNumber.value;) {
    // 随机数找index
    const index = Math.random()*total.value >>0
    if (arr[index] < 9) {
      // 此处没炸弹则放入炸弹，并循环数减1
      b++
      arr[index] = 9
      const units:number = index%columns.value
      const tens:number = index/columns.value >>0
      list.map(i => {
        const y:number = tens+i[0] >= 0 ? (tens+i[0] < rows.value ? tens+i[0] : -1) :-1
        const x:number = units+i[1] >= 0 ? (units+i[1] < columns.value ? units+i[1] : -1) :-1
        if (x>=0 && y>=0){
          arr[y * columns.value +x]++
        }
      })
    }
  }
  grid.value = arr
  // 非初始化时，gridItems有内容了，记得重置打开状态
  if (gridItems.value) {
    console.log('reset')
    for(let item of gridItems.value) {
      item.reset()
    }
  }
}
// 关闭游戏
const stopGame = () => {
  isGameOver.value = true
  console.log('关闭游戏,isGameOver:', isGameOver.value)
}
// 自定义事件
const onItemFlag = (item: number, index: number, value: number) => {
  value ? flagNum.value += 1 : flagNum.value -= 1

  console.log('位置', index, 'item', item)
}
const onItemOpen = (item: number, index: number) => {
  // 开始及时
  !time.value && timing()
  // console.log(item)
  if(item >= 9){
    return stopGame()
  }
  opened.value += 1
  console.log(opened.value, 'opened')
  if(opened.value === total.value - boobNumber.value) {
    onSuccess()
  }
  openGridItem(item, index)
}
const openGridItem = (item: number, index: number) => {
  console.log("序号：",index,"坐标，",index%columns.value,".",index/columns.value >>0,)
  if(item) {
    return // 有数字就停止
  }
  const units:number = index%columns.value
  const tens:number = index/columns.value >>0
  const list: number[][] = [[-1,-1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]
  list.forEach((i) => {
    const y:number = tens+i[0] >= 0 ? (tens+i[0] < rows.value ? tens+i[0] : -1) :-1
    const x:number = units+i[1] >= 0 ? (units+i[1] < columns.value ? units+i[1] : -1) :-1
    if (x>=0 && y>=0){
      // console.log(x, y)
      const gridItem = gridItems.value[y * columns.value + x]
      gridItem.open()
    }
  })
}
const onItemOpenAll = (item: number, index: number) => {
  if(item === 0) return
  const itemArr: any[] = []
  let count = 0 // 记录周围棋子数
  const units:number = index%columns.value
  const tens:number = index/columns.value >>0
  const list: number[][] = [[-1,-1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]
  list.forEach((i) => {
    const y:number = tens+i[0] >= 0 ? (tens+i[0] < rows.value ? tens+i[0] : -1) :-1
    const x:number = units+i[1] >= 0 ? (units+i[1] < columns.value ? units+i[1] : -1) :-1
    if (x>=0 && y>=0){
      const gridItem = gridItems.value[y * columns.value + x]
      // console.log(gridItem)
      if (gridItem.isFlag) {
        count++
      } else {
        itemArr.push(gridItem)
      }
    }
  })
  // console.log('count', count, item)
  if (count === item) {
    itemArr.forEach(item => {
      item.open()
    })
  }
}
// 修改难度
const changeLevel = (index: number) => {
  if (index === level.value) {
    return
  }
  level.value = index
  if (index === 0) {
    rows.value = 10 // 行数
    columns.value = 10 // 列数
    boobNumber.value = 10 // 炸弹数
    doStartGame(null)
  } else if (index === 1) {
    rows.value = 16 // 行数
    columns.value = 16 // 列数
    boobNumber.value = 40 // 炸弹数
    doStartGame(null)
  } else if (index === 2) {
    rows.value = 16 // 行数
    columns.value = 30 // 列数
    boobNumber.value = 99 // 炸弹数
    doStartGame(null)
  }
}
// 计时
const timing = () => {
  const startDate = new Date().getTime()
  const re = setInterval(() => {
    const endDate = new Date().getTime()
    time.value = endDate - startDate
    // console.log('time', time.value)
    if (!opened.value) {
      clearInterval(re)
      time.value = 0
    }
    if (isGameSuccess.value || isGameOver.value || time.value >= (100 * 60 * 1000)-1) {
      clearInterval(re)
    }
  }, 50)
}
// 时间戳差值的换算
const convertTime = (time:number) => {
  let min = (time/1000/60 >>> 0).toString().padStart(2, '0')
  let second = ((time/1000 >>> 0)%60).toString().padStart(2, '0')
  let milliseconds = (time%1000).toString().padStart(3, '0')
  if (time >= (100 * 60 * 1000)-1) {
    return '99:59:999'
  } else {
    return min + ':' + second + ':' + milliseconds
  }
}
// 胜利触发
const onSuccess = () => {
    isGameSuccess.value = true
    console.log('isGameSuccess:', isGameSuccess.value)
    confetti.value = true // 彩条
    showCommit.value = true
}
// 提交成绩
const onCommit = () => {
  
  console.log('名字', inputValue.value, inputValue,  '成绩', time.value, '难度', level.value)
  commitLoading.value = true
let data = qs.stringify({
  'name': inputValue.value,
  'difficulty': level.value,
  'time': time.value
});

let config = {
  method: 'post',
  maxBodyLength: Infinity,
  url: 'http://43.138.53.139:3001/minesweeper/add',
  headers: { 
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  data : data
};

axios.request(config)
.then((response: { data: any }) => {
  console.log(JSON.stringify(response.data));
  alert(response.data.message)
  commitLoading.value = false
  showCommit.value = false
  getRankList()
})
.catch((error: any) => {
  alert("好像出什么问题了")
  commitLoading.value = false
  showCommit.value = false
  console.log(error);
});
}
// 排行榜相关参数
type Rank = {
  name: string,
  time: number,
  ctime: string
}
type RankList = [Rank[], Rank[], Rank[]]
const rankList = reactive<RankList>([[], [], []])
const rankLoading = ref<boolean>(false)
const showRankList = ref<boolean>(false)
// 获取排行榜
const difficultyList = ['简单', '中等', '困难']
const getRank = (difficulty:number) => {
rankLoading.value = true
let data = qs.stringify({
  'difficulty': difficulty
});

let config = {
  method: 'post',
  maxBodyLength: Infinity,
  url: 'http://43.138.53.139:3001/minesweeper/get',
  headers: { 
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  data : data
};

axios.request(config)
.then((response: { data: any }) => {
  console.log(JSON.stringify(response.data));
  rankList[difficulty] = response.data.data
  rankLoading.value = false
})
.catch((error: any) => {
  alert("好像出什么问题了")
  rankLoading.value = false
  console.log(error);
});
}
const getRankList = () => {
  difficultyList.forEach((item, key) => {
    getRank(key)
  })            
}
const showRank = () => {
  Loading()
  getRank(level.value)
  showRankList.value = true
}
const loadCount = ref<number>(1)
const loadStr = ref<string>('.')
const Loading = () => {
  const interval =  setInterval(() => {
    if (!rankLoading.value) console.log("暂停loading"),clearInterval(interval)
    loadCount.value ++
    if (loadCount.value < 6) {
      loadStr.value += '.'
    } else {
      loadCount.value = 1
      loadStr.value = '.'
    }
          }, 500)
}
// 初始化
doStartGame(null)
</script>

<template>
  <div class="main">
    <div class="commit" v-if="showCommit">
      <div class="input-group">
        <div class="input-title">大侠请留名</div>
        <input type="text" v-model="inputValue" placeholder="无名">
        <button v-if="!commitLoading" @click="onCommit()">上榜！</button>
        <button v-else>Loading......</button>
      </div>
    </div>
    <div class="rankArea" v-if="showRankList">
      <div class="rankList">
        <div class="button">
          <button @click="showRankList = false">×</button>
        </div>
        <div class="title">{{ difficultyList[level] }}</div>
        <div class="rank-box" v-show="!rankLoading">
          <div class="list" v-for="(item, index) in rankList[level]" :key="index">
            <span class="index">{{ index + 1}}</span>
            <span class="name">{{ item.name || '无名客' }}</span>
            <span class="time">{{ convertTime(item.time) }}</span>
            <span class="ctime">{{ item.ctime.substring(0,10) }}</span>
          </div>  
        </div>
        <div class="rank-load" v-show="rankLoading">
          正在从天界获取{{ loadStr }}
        </div>
      </div>
    </div>
    <ConfettiExplosion :particleCount="300" :stageHeight="500" v-if="confetti" />
    <div class="rank-button">
      <span @click="showRank()">江湖传闻</span>
    </div>
    <div class="setting-list">
      <button :class="{'active' : level === 0}" @click="changeLevel(0)">简单</button>
      <button :class="{'active' : level === 1}" @click="changeLevel(1)">中等</button>
      <button :class="{'active' : level === 2}" @click="changeLevel(2)">困难</button>
    </div>
    <div class="main-list">
      <div class="remain">
        剩余地雷：{{ boobNumber - flagNum }}
      </div>
      <button type="button" class="start" @click="doStartGame">
        <span v-show="isGameOver">ReStart</span>
        <span v-show="isGameSuccess">Success</span>
        <span v-show="!(isGameOver || isGameSuccess)">Start</span>
      </button>
      <div class="time">
        计时：{{ convertTime(time) }}
      </div>
    </div>
    <div id="grid-game" :class="{'game-over' : isGameOver}" :style="gridStyle">
      <GridItem
        v-for="(item, index) in grid"
        ref="gridItems"
        :key="index"
        :item="item"
        @open="onItemOpen(item, index)"
        @flag="onItemFlag(item, index, $event)"
        @openAll="onItemOpenAll(item, index)"
      >
      </GridItem>
    </div>
  </div>
</template>

<style>
/* .main {
  overflow: hidden;
} */
.rank-button {
  text-align: right;
}
.rank-button span {
  font-size: 12px;
  color: rosybrown;
  cursor: pointer;
}
.setting-list {
  padding: 15px;
}
.setting-list button {
  margin: 10px;
}
.setting-list button.active {
  background-color: #000;
  color: #fff;
}
.main-list {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
:root {
  --row: 10;
  --column: 10;
}
#grid-game {
  margin: auto;
  width: 100%;
  cursor: pointer;
  display: grid;
  justify-content: center;
  grid-template-rows: repeat(var(--row), 40px );
  grid-template-columns: repeat(var(--column), 40px );
}
.start {
  margin-bottom: 1rem;
}
.game-over {
  pointer-events: none;
}
/* 上榜留名 */
.commit {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: 99;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: rgba(255, 255, 255, 0.8);
}
.commit .input-group {
  width: 400px;
  height: 300px;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
}
.commit .input-group .input-title {
  font-size: 34px;
}
.commit .input-group button {
  font-weight: bold;
  width: 150px;
  margin: 0 auto;
  /* border: 1px solid #666666; */
}
.commit .input-group input {
  text-align: center;
  line-height: 36px;
  font-size: 28px;
  height: 44px;
}
/* 排行榜 */
.rankArea {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: 99;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: rgba(255, 255, 255, 0.8);
}
.rankList .button {
  text-align: right;
}
.rankList .list {
  border-bottom: 1px dashed #aaa;
}
.rankList .list span {
  display: inline-block;
  max-width: 140px;
  margin-right: 15px;
}
</style>
